Previous Book Contents Book Index Next

Inside Macintosh: QuickDraw GX Programmer's Overview / Part 2 - The QuickDraw GX Programming Cookbook
Chapter 6 - Handling Graphics


Dragging Shapes Using Offscreen Bitmaps

This programming recipe creates a window with three colored circles--one red, one green, and one blue. The user can drag any of the circles around the window. Since the circles are drawn with the add transfer mode, the colors are added together wherever the circles overlap and the area where all three circles overlap becomes white. Figure 6-5 shows a black-and-white representation of this sample program. For a color version of the three circles, see Plate 4 at the front of this book.

Figure 6-5 Dragging colored circles drawn with an add transfer mode

The sample program described in this recipe provides feedback as the user drags the selected circle. To provide for smooth redrawing, this recipe uses offscreen bitmaps. When the user presses the mouse button down on a circle, the application creates two offscreen bitmaps:

As the user drags the mouse, these actions occur:

  1. The background offscreen bitmap is copied into the main offscreen bitmap.
  2. The selected circle is drawn into the main offscreen bitmap--over the background that has already been drawn there.
  3. The contents of the main offscreen bitmap are copied to the window.

Overview of Recipe Steps

The steps in this recipe show you how to:

    1. Create a document information structure
    2. Create a picture with three colored circles
    3. Create a bitmap shape for offscreen drawing
    4. Create two offscreen drawing environments
    5. Initialize the background offscreen bitmap
    6. Respond to mouse clicks
    7. Hit-test the picture
    8. Extract the selected circle from the picture
    9. Allow the user to drag the selected circle
    10. Dispose of objects that are no longer needed

You need to follow all of the steps in this recipe to implement dragging feedback with offscreen bitmaps.

Functions Used in This Recipe

QuickDraw GX functions used in this recipe:
GXNewShape"Shape Objects"
QuickDraw GX Objects
GXNewBitmap"Bitmap Shapes"
QuickDraw GX Graphics
GXCopyToShape"Shape Objects"
QuickDraw GX Objects
GXGetPictureParts"Picture Shapes"
QuickDraw GX Graphics
GXSetPictureParts"Picture Shapes"
QuickDraw GX Graphics
GXSetShapeAttributes"Shape Objects"
QuickDraw GX Objects
GXSetShapeColor"Ink Objects"
QuickDraw GX Objects
GXSetShapeHitTest"Transform Objects"
QuickDraw GX Objects
GXSetShapeTransform"Shape Objects"
QuickDraw GX Objects
GXSetShapeViewPorts"Transform Objects"
QuickDraw GX Objects
GXDrawShape"Shape Objects"
QuickDraw GX Objects
GXHitTestPicture"Picture Shapes"
QuickDraw GX Graphics
GXDisposeShape"Shape Objects"
QuickDraw GX Objects
GXDisposeViewPort"View-Related Objects"
QuickDraw GX Objects

Standard Macintosh functions used in this recipe:
GetCTable"Color QuickDraw"
Imaging With QuickDraw
DisposCTable"Color QuickDraw"
Imaging With QuickDraw
DisposeWindow"Window Manager"
Macintosh Toolbox Essentials

This recipe gives a brief description of these functions; you can find complete reference information for these functions in the Inside Macintosh suite of books.

This recipe also uses functions from the QuickDraw GX libraries:
SetShapeCommonTransfertransferMode library
CTableToColorSetqd library
CreateOffscreenoffscreen library
DisposeOffscreenoffscreen library
NewOvaloval library

Recipe Step Descriptions

In this section, each step is described individually.

  1. Create a document information structure

    You need to create a document information structure for the application discussed in this recipe just as you did for the application in the "Creating Geometric Shapes" recipe on page 179. Since the application discussed here doesn't allow the user to create shapes, the document information structure can be a subset of the one described in the earlier recipe. In fact, it only needs three fields:

    typedef struct {
    CWindowRecord window;
    gxViewPort viewPort;
    gxShape picture;
    } DocumentInfo, *DocumentPtr;

    You initialize the window and view port fields of this structure as you did on page 184.

  2. Create a picture with three colored circles

    To initialize the document's picture, you need to create a new picture shape:

    gCurrent->picture = GXNewShape(gxPictureType);

    Since you will be copying shapes into this picture, you need to set the unique items shape attribute of the picture, which indicates that shapes added to the picture should be copied, rather than merely referenced by
    the picture:

    GXSetShapeAttributes(gCurrent->picture, gxUniqueItemsShape);

    Then, you add a black background to the picture:

    gxRectangle bounds;
    gxShape boundsShape;

    GetWindowBounds(&bounds, gCurrent->window);
    boundsShape = GXNewRectangle(&bounds);
    GXSetShapeColor(boundsShape, &gxBlack);
    GXSetShapeHitTest(boundsShape, gxNoPart, 0);
    GXSetPictureParts(gCurrent->picture, 0, 0, 1,
    &boundsShape, nil, nil, nil);

    The black background is a rectangle shape that fills the window. The hit-test parameters are set to the gxNoPart constant, because you do not want the user to be able to hit (and therefore drag) the background.

    You could dispose of the boundsShape rectangle now, since it has been copied into the picture, but it will come in handy a little later on.

    Next, you can add the three colored circles. First you create a color structure:

    gxColor rgbColor;

    rgbColor.space = gxRGBSpace;
    rgbColor.profile = nil;
    rgbColor.element.rgb.red = 0xFFFF;
    rgbColor.element.rgb.green = 0x0000;
    rgbColor.element.rgb.blue = 0x0000;

    Then you create a circle shape using the NewOval library function from the QuickDraw GX oval library:

    gxShape tempCircle; 

    tempCircle = NewOval(&circleGeometry);
    GXSetShapeColor(tempCircle, &rgbColor);
    GXSetShapeHitTest(tempCircle, gxGeometryPart, ff(1));

    Notice that the hit-test parameters of this circle shape are set to the gxGeometryPart constant. By setting the hit-test parameters to this value, you are indicating that QuickDraw GX should hit-test the circle shape; by setting the tolerance hit-test parameter to 1, you are indicating that the hit point should be very close to the geometry of the circle to be considered a hit.

    To make the application draw the circles so that their colors are added together wherever they overlap, use the function SetShapeCommonTransfer from the QuickDraw GX transfer mode library:

    SetShapeCommonTransfer(tempCircle, gxAddMode);

    This function sets the transfer mode property of the circle shape's ink object so that whenever you draw the circle, QuickDraw GX renders it by adding the color of its pixels to the colors of the pixels that you draw the shape over.

    Now that you've initialized this circle shape, you need to add three copies of the shape to the window picture:

    /* copy red circle into picture */
    GXSetPictureParts(gCurrent->picture, 0, 0, 1
    &tempCircle, nil, nil, nil);

    /* change colors and copy into picture again */
    rgbColor.element.rgb.red = 0x0000;
    rgbColor.element.rgb.green = 0xFFFF;
    GXSetShapeColor(tempCircle, &rgbColor);
    GXSetPictureParts(gCurrent->picture, 0, 0, 1
    &tempCircle, nil, nil, nil);

    /* change colors and copy into picture again */
    rgbColor.element.rgb.green = 0x0000;
    rgbColor.element.rgb.blue = 0xFFFF;
    GXSetShapeColor(tempCircle, &rgbColor);
    GXSetPictureParts(gCurrent->picture, 0, 0, 1
    &tempCircle, nil, nil, nil);

    After copying the circle shape into the picture three times, you can dispose of the original circle shape:

    GXDisposeShape(tempCircle);
  3. Create a bitmap shape for offscreen drawing

    Before you can create an offscreen bitmap, you need to create a bitmap structure that defines its and makes its color set match the color set of the window into which you'll be copying the offscreen drawings.

    The first task is to use the standard Macintosh function GetCTable to
    get the standard Macintosh version of a color set, and then use the CTableToColorSet function from the QuickDraw GX qd library to
    convert it to a QuickDraw GX color set:

    CTabHandle standardClut;
    gxColorSet setOfColors;

    standardClut = GetCTable(72);
    setOfColors = CTableToColorSet(standardClut);
    DisposCTable(standardClut);

    Now create and initialize a bitmap structure so that the bitmap matches the size of the window and its color set matches the colors in the setOfColors color set:

    WindowPtr theWindow;
    Rect windowRect;
    gxBitmap aBitmap;
    gxPoint bitmapPosition;

    theWindow = gCurrent->window;
    windowRect = (*theWindow).portRect;

    aBitmap.image = nil;
    aBitmap.width = windowRect.right - windowRect.bottom;
    aBitmap.height = windowRect.bottom - windowRect.top;
    aBitmap.rowBytes = 0;
    aBitmap.pixelSize = 8;
    aBitmap.space = gxIndexedSpace;
    aBitmap.set = setOfColors;
    aBitmap.profile = nil;

    bitmapPosition.x = bitmapPosition.y = ff(0);

    When the image field is set to nil, QuickDraw GX allocates the pixel image for you in QuickDraw GX memory. You can create a bitmap shape from this bitmap structure using this code:

    gxShape bitmapShape;

    bitmapShape = GXNewBitmap(&aBitmap, &bitmapPosition);
  4. Create two offscreen drawing environments

    QuickDraw GX provides the offscreen library to help you create offscreen drawing environments. Its central data structure is the offscreen structure, which has five fields:

    • The draw field contains a reference to the offscreen bitmap shape.
    • The device field contains a reference to the offscreen view device object, which in turn contains a reference to the bitmap.
    • The group field contains a reference to the offscreen view group object, which contains the offscreen view device and associates it with the offscreen view port.
    • The port field contains a reference to the offscreen view port object. When you draw a shape to this view port, QuickDraw GX renders the shape into the pixel image of the offscreen bitmap.
    • The xform field contains a reference to the offscreen transform, which references the offscreen view port. You can connect your shapes to this transform object and then, when you draw them, QuickDraw GX renders them into the pixel image of the offscreen bitmap.

      This recipe uses a scheme called double-buffering to produce smooth feedback as the user drags a circle around the window. With double-buffering, you create two separate offscreen bitmaps. One bitmap contains the back-
      ground--everything in the window besides the circle being dragged. The other bitmap is the main offscreen bitmap. When the user is dragging the circle, you copy the background bitmap into the main offscreen bitmap, draw the circle at its current location also into the main offscreen bitmap, and then copy the main offscreen bitmap to the window.

      To create two offscreen environments, you first declare two offscreen structures:

      offscreen gAnimation, gAnimationBackground;

      Then you use the offscreen library function CreateOffscreen. This function copies a reference of a bitmap shape into the offscreen structure, and also initializes the corresponding view device, view group, view port, and transform objects.

      CreateOffscreen(&gAnimation, aBitmap);

      Then you can dispose of your reference to that bitmap shape and create a second offscreen structure:

      GXDisposeShape(aBitmap);

      bitmapShape = GXNewBitmap(&aBitmap, &bitmapPosition);
      CreateOffscreen(&gAnimationBackground, aBitmap);
      GXDisposeShape(aBitmap);

      You can now dispose of your reference to the color set you created, because both of the bitmaps have their own reference to this color set:

      GXDisposeColorSet(setOfColors);

      Later in this application, when you draw the background offscreen bitmap, you want it to draw into the main offscreen bitmap. You can prepare for this by setting the transform object of the background offscreen bitmap:

      GXSetShapeTransform(gAnimationBackground.draw,
      gAnimation.xform);

      After this call to the GXSetShapeTransform function, the transform of
      the background offscreen bitmap references the view port of the main offscreen bitmap. Therefore, whenever you draw the background offscreen bitmap, QuickDraw GX copies its pixels into the pixel image of the main offscreen bitmap.

      When you draw the main offscreen bitmap, you want it to draw into the window so first you need to set the view port of the main offscreen bitmap to be the window view port:

      GXSetShapeViewPorts(gAnimation.draw, 1, &(gCurrent->viewPort));

      After this call to the GXSetShapeViewPorts function, the transform of the main offscreen bitmap references the window view port. Therefore, when you draw the main offscreen bitmap, QuickDraw GX copies its pixels into the window.

  5. Initialize the background offscreen bitmap

    Now that you've created two offscreen drawing environments, you can initialize the color value of all the background offscreen bitmap pixels to white by setting the color of the boundsShape rectangle you created in Step 2 to white, like this:

    gxShape boundsCopy;

    boundsCopy = GXCopyToShape(nil, boundsShape);
    GXSetShapeColor(boundsCopy, &gWhite);

    The next step in initializing is to set the transform of the boundsShape rectangle:

    GXSetShapeTransform(boundsCopy, gAnimationBackground.xform);

    Now, when you draw the rectangle, QuickDraw GX renders it into the pixels of the background offscreen bitmap:

    GXDrawShape(boundsCopy);

    Once you've drawn it, you can dispose of the boundsCopy rectangle:

    GXDisposeShape(boundsCopy);
  6. Respond to mouse clicks

    When the user presses the mouse button, translate the coordinates provided in the event record to QuickDraw GX local coordinates as described on page 185, and then call the MyHandleDragCircle function. This function provides feedback as the user drags the mouse.

    Here is the flow of control of the HandleDragCircle function:

    void MyHandleDragCircle(gxPoint *originalPoint) 
    {
    gxPoint oldPoint;
    gxPoint newPoint

        int whichCircle;

        gxShape selectedCircle;
    gxShape backgroundPicture;

    if (MyUserSelectedACircle(originalPoint, whichCircle)) {

            /* Extract selected circle -- see Step 7.*/ 
    /* Provide dragging feedback -- see Steps 8 & 9. */
    }
    }

    Notice the selectedCircle and backgroundPicture shape references. Later steps use these shapes to extract the selected circle from the picture for the double-buffering mechanism.

    The next three steps--Steps 7 through 9--show how to implement the parts of the MyHandleDragCircle function.

  7. Hit-test the picture

    The MyUserSelectedACircle function hit-tests the window picture and returns true as the function result if the hit point hits one of the circles in
    the picture:

    boolean MyUserSelectedACircle(gxPoint *hitPoint, 
    int *whichCircle)
    {
    boolean anyCirclesHit;
    gxHitTestInfo result;

        anyCirclesHit = GXHitTestPicture(gCurrent->picture,
    &hitPoint,
    &result, 1, 1);

     if (anyCirclesHit)
    *whichCircle = result.containerIndex;

        return anyCirclesHit;
    }

    This function also returns an integer that indicates which of the three circles was hit. The GXHitTestPicture function determines this index for you, and returns it in the containerIndex field of the result parameter.

  8. Extract the selected circle from the picture

    Once you've determined which circle was hit, you can store a reference to the circle shape in your selectedCircle variable using the GXGetPictureParts function:

    GXGetPictureParts(gCurrent->picture,
    whichCircle,
    1,
    &selectedCircle,
    nil, nil, nil);

    You want to draw the selected circle in the main offscreen bitmap, so you can set its transform object accordingly using this call:

    GXSetShapeTransform(selectedCircle, gAnimation.xform);

    You also need to create a background picture that contains everything in
    the window picture except the selected circle. First you copy the entire window picture:

    backgroundPicture = GXCopyToShape(nil, gCurrent->picture);

    Then you use the GXSetPictureParts function to remove the selected circle:

    GXSetPictureParts(backgroundPicture, 
    whichCircle,
    1, /* remove one picture item */
    0, /* replace it with nothing */
    nil, nil, nil, nil);

    Since you are drawing the background picture into the background bitmap, you can set its transform accordingly:

    GXSetShapeTransform(backgroundPicture,
    gAnimationBackground.xform);

    Now, to render the background picture into the background bitmap, you just need to draw it:

    GXDrawShape(backGroundPicture);

    Once you've rendered the background picture, you no longer need the picture shape object, so you can dispose of it:

    GXDisposeShape(backgroundPicture);
  9. Allow the user to drag the selected circle

    Now you can provide the dragging feedback with a simple while loop:

    oldPoint.x = originalPoint->x;
    oldPoint.y = originalPoint->y;

    while (WaitMouseUp()) {

        GXGetViewPortMouse(gCurrent->viewPort, &newPoint);

        if (MouseMoved(newPoint, oldPoint)) {
    GXDrawShape(gAnimationBackground.draw);
    GXMoveShape(selectedCircle,
    newPoint.x - oldPoint.x,
    newPoint.y - oldPoint.y);
    GXDrawShape(selectedCircle);
    GXDrawShape(gAnimation.draw);
    }
    oldPoint = newPoint;
    }

    While the user is dragging the mouse, the code in this while loop draws the background offscreen bitmap, which renders it into the main offscreen bitmap. Then the code moves the selected circle to the new mouse position and draws it into the main offscreen bitmap which transfers the offscreen image to the window.

  10. Dispose of objects and exit the program

    When it's time to quit your application, dispose of the picture shape you created for the window:

    GXDisposeShape(gCurrent->picture);

    You' also need to dispose of your offscreen objects. The offscreen library provides the DisposeOffscreen function for you:

    DisposeOffscreen(gAnimation);
    DisposeOffscreen(gAnimationBackground);

    Finally, dispose of your window view port and your window:

    GXDisposeViewPort(gCurrent->viewPort);
    DisposeWindow((CWindowPtr) gCurrent);

Related Recipes

The previous recipes in this chapter show how you can create, select, edit, and transform path shapes. These recipes provide feedback by redrawing the path shape with an exclusive-OR transfer mode.

The recipes in Chapter 4, "Using the QuickDraw GX Environment," show you how to initialize QuickDraw GX and set up the QuickDraw GX debugging facilities. You should read the recipes in that chapter before using any recipes in this chapter.

The recipes in Chapter 5, "Using Macintosh Windows," show you how to create Macintosh windows, attach QuickDraw GX view ports to them, and implement zooming, resizing, and scrolling. You need to be familiar with the information in that chapter before you can display QuickDraw GX graphics in a Macintosh window.

The recipes in Chapter 7, "Handling Typography," show you how to create and manipulate typographic shapes.

The recipes in Chapter 8, "Printing," show you how to send your graphics and typographic shapes to a printer.


Previous Book Contents Book Index Next

© Apple Computer, Inc.
8 JUL 1996